﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenCvSharp;
using VideoInputSharp;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;

namespace ExpreRec.Logic.ImageAcquisition
{
    public class CameraImageReader : IDisposable
    {
        // A inner delegate class used in invoke, like function pointer
        private delegate void RefreshAction();

        private int deviceID;
        private int captureFps;
        private int captureWidth;
        private int captureHeight;

        private PictureBox pb;
        private RefreshAction refreshAction;

        private bool started = false;

        private VideoInput vi;
        private IplImage currentImage = null;

        private Thread innerThread;

        public CameraImageReader(PictureBox pb = null, int deviceID = 0, int captureFps = 30, int captureWidth = 640, int captureHeight = 480)
        {
            this.deviceID = deviceID;
            this.captureFps = captureFps;
            this.captureWidth = captureWidth;
            this.captureHeight = captureHeight;
            this.pb = pb;

            // picturebox is the picture container of the camera
            if (pb != null)
            {
                // if a picturebox is provided, a delegate function pointer should be created
                refreshAction = new RefreshAction(pb.Refresh);
            }


            // create the new video input, from VideoInputSharp library
            vi = new VideoInput();

            // set the parameters
            vi.SetIdealFramerate(deviceID, captureFps);
        }

        public void Start()
        {
            if (!started)
            {
                // start the device, will return the result of success or not
                if (vi.SetupDevice(deviceID, captureWidth, captureHeight))
                    started = true;

                // an inner thread started for pulling the pictures from the camera
                // if a picturebox is provided, it will update it as well
                if (started)
                {
                    innerThread = new Thread(new ThreadStart(threadMain));
                    innerThread.Name = "ThreadCamera";
                    innerThread.Start();
                }
            }
        }

        public void Stop()
        {
            if (started)
            {
                // set the stop flag, and stop the device
                started = false;

                vi.StopDevice(deviceID);
            }
        }

        public IplImage GetImage()
        {
            if (started)
            {
                // return the neweast picture
                while (currentImage == null) ;
                return currentImage;
            }
            else return null;
        }

        private void threadMain()
        {
            // image container img
            // bitmap contianer used to be updated on picture box
            using (IplImage img = new IplImage(captureWidth, captureHeight, BitDepth.U8, 3))
            using (Bitmap bitmap = new Bitmap(captureWidth, captureHeight, PixelFormat.Format24bppRgb))
            {
                // update the picturebox container to bitmap's reference
                // newest updated picture will be extracted to this position
                if (pb != null)
                    pb.Image = bitmap;

                // if started flag be false, stop the loop
                while (started)
                {
                    // if we have a new picture generated in the camera
                    if (vi.IsFrameNew(deviceID))
                    {
                        // get the image, write it to img
                        vi.GetPixels(deviceID, img.ImageData, false, true);

                        // convert and write to bitmap
                        // the picturebox's image will be updated
                        img.ToBitmap(bitmap);

                        // update the current newest image this reader keeps
                        currentImage = img.Clone();

                        // pb.Refresh(), picturebox should be refreshed to display the new image
                        // but can't be called in this thread, must be called
                        // in the thread created pb
                        // we use an invoke method to call, to delegate the function
                        // to the main thread (the thread created the delegate function), the same
                        // thread created the picturebox
                        if (refreshAction != null && started)
                        {
                            try
                            {
                                pb.Invoke(refreshAction);
                            }
                            catch (InvalidOperationException) { }
                        }
                    }
                }
            }
        }

        public void Dispose()
        {
            // release the resources
            if (started)
                Stop();
            if (vi != null)
            {
                vi.Dispose();
            }
        }
    }
}
